package com.springboot.mybatis.common.util;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

public class Remote {
    public static String POSTMethod(String url, Map<String, Object> argsMap,
                                    String content) throws Exception {
        byte[] dataByte = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
            if (MapUtils.isNotEmpty(argsMap)) {
                UrlEncodedFormEntity encodedFormEntity = new UrlEncodedFormEntity(
                        setHttpParams(argsMap), "utf-8");
                httpPost.setEntity(encodedFormEntity);
            }
            if (StringUtils.isNotEmpty(content)) {
                httpPost.setEntity(new ByteArrayEntity(content.getBytes("utf-8")));
            }
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) {

                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    dataByte = getData(httpEntity);
                    httpPost.abort();
                }
                return bytesToString(dataByte);
            }
        }

    }

    public static String POSTMethod(String url, Map<String, Object> argsMap,
                                    String content, String cookies) throws Exception {
        byte[] dataByte = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            if (MapUtils.isNotEmpty(argsMap)) {
                UrlEncodedFormEntity encodedFormEntity = new UrlEncodedFormEntity(
                        setHttpParams(argsMap), "UTF-8");
                httpPost.setEntity(encodedFormEntity);
            }
            if (StringUtils.isNotEmpty(content)) {
                httpPost.setEntity(new ByteArrayEntity(content.getBytes()));
            }

            if (StringUtils.isNotEmpty(cookies)) {
                httpPost.setHeader("Cookie", cookies);
                httpPost.setHeader("Accept", "application/json");
                httpPost.setHeader("Content-Type", "application/json");
            }

            try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) {

                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    dataByte = getData(httpEntity);
                    httpPost.abort();
                }

                return bytesToString(dataByte);
            }
        }
    }

    public static String GETMethod(String url, Map<String, Object> argsMap)
            throws Exception {
        byte[] dataByte = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {

            url = formatGetParameter(url, argsMap);
            HttpGet httpGet = new HttpGet(url);
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    dataByte = getData(httpEntity);
                    httpGet.abort();
                }
                return bytesToString(dataByte);
            }
        }
    }

    public static String PUTMethod(String url, Map<String, Object> argsMap,
                                   String cookies, String content) throws Exception {
        byte[] dataByte = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPut httpPut = new HttpPut(url);

        if (StringUtils.isNotEmpty(content)) {
            httpPut.setEntity(new ByteArrayEntity(content.getBytes()));
        }

        if (StringUtils.isNotEmpty(cookies)) {
            httpPut.setHeader("Cookie", cookies);
            httpPut.setHeader("Accept", "application/json");
            httpPut.setHeader("Content-Type", "application/json");
        }

        if (MapUtils.isNotEmpty(argsMap)) {
            UrlEncodedFormEntity encodedFormEntity = new UrlEncodedFormEntity(
                    setHttpParams(argsMap), "UTF-8");
            httpPut.setEntity(encodedFormEntity);
        }

        CloseableHttpResponse httpResponse = httpClient.execute(httpPut);

        HttpEntity httpEntity = httpResponse.getEntity();
        if (httpEntity != null) {
            dataByte = getData(httpEntity);
            httpPut.abort();
        }

        String result = bytesToString(dataByte);
        httpResponse.close();
        httpClient.close();
        return result;
    }

    public static String PUTMethod(String url, Map<String, Object> argsMap,
                                   Map<String, String> headerParam, String content) throws Exception {
        byte[] dataByte = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPut httpPut = new HttpPut(url);

        if (StringUtils.isNotEmpty(content)) {
            httpPut.setEntity(new ByteArrayEntity(content.getBytes()));
        }

        if (MapUtils.isNotEmpty(headerParam)) {
            Set entrySet = headerParam.entrySet();
            Iterator entryIter = entrySet.iterator();
            while (entryIter.hasNext()) {
                Map.Entry entry = (Map.Entry) entryIter.next();
                String key = (String) entry.getKey();
                String value = (String) entry.getValue();
                httpPut.setHeader(key, value);
            }
        }

        if (MapUtils.isNotEmpty(argsMap)) {
            UrlEncodedFormEntity encodedFormEntity = new UrlEncodedFormEntity(
                    setHttpParams(argsMap), "UTF-8");
            httpPut.setEntity(encodedFormEntity);
        }

        CloseableHttpResponse httpResponse = httpClient.execute(httpPut);

        HttpEntity httpEntity = httpResponse.getEntity();
        if (httpEntity != null) {
            byte[] responseBytes = getData(httpEntity);
            dataByte = responseBytes;
            httpPut.abort();
        }

        String result = bytesToString(dataByte);
        httpResponse.close();
        httpClient.close();
        return result;
    }

    public static String DELETEMethod(String url, Map<String, Object> argsMap,
                                      Map<String, String> headerParam) throws Exception {
        byte[] dataByte = null;
        url = formatGetParameter(url, argsMap);
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpDelete httpDelete = new HttpDelete(url);

        if (MapUtils.isNotEmpty(headerParam)) {
            Set entrySet = headerParam.entrySet();
            Iterator entryIter = entrySet.iterator();
            while (entryIter.hasNext()) {
                Map.Entry entry = (Map.Entry) entryIter.next();
                String key = (String) entry.getKey();
                String value = (String) entry.getValue();
                httpDelete.setHeader(key, value);
            }
        }

        CloseableHttpResponse httpResponse = httpClient.execute(httpDelete);

        HttpEntity httpEntity = httpResponse.getEntity();
        if (httpEntity != null) {
            byte[] responseBytes = getData(httpEntity);
            dataByte = responseBytes;
            httpDelete.abort();
        }

        String result = bytesToString(dataByte);
        httpResponse.close();
        httpClient.close();
        return result;
    }

    public static String formatGetParameter(String url,
                                            Map<String, Object> argsMap) {
        if ((url != null) && (url.length() > 0)) {
            if (!url.endsWith("?")) {
                url = url + "?";
            }
            if ((argsMap != null) && (!argsMap.isEmpty())) {
                Set entrySet = argsMap.entrySet();
                Iterator iterator = entrySet.iterator();
                while (iterator.hasNext()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    if (entry != null) {
                        String key = (String) entry.getKey();
                        String value = (String) entry.getValue();
                        url = url + key + "=" + value;
                        if (iterator.hasNext()) {
                            url = url + "&";
                        }
                    }
                }
            }
        }
        return url;
    }

    public static byte[] getData(HttpEntity httpEntity) throws Exception {
        BufferedHttpEntity bufferedHttpEntity = new BufferedHttpEntity(
                httpEntity);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        bufferedHttpEntity.writeTo(byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();
    }

    public static List<BasicNameValuePair> setHttpParams(
            Map<String, Object> argsMap) {
        List nameValuePairList = new ArrayList();

        if ((argsMap != null) && (!argsMap.isEmpty())) {
            Set set = argsMap.entrySet();
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry) iterator.next();
                BasicNameValuePair basicNameValuePair = new BasicNameValuePair(
                        (String) entry.getKey(), entry.getValue().toString());
                nameValuePairList.add(basicNameValuePair);
            }
        }
        return nameValuePairList;
    }

    public static String bytesToString(byte[] bytes)
            throws UnsupportedEncodingException {
        if (bytes != null) {
            String returnStr = new String(bytes, "utf-8");
            returnStr = StringUtils.trim(returnStr);
            return returnStr;
        }
        return null;
    }

    public static String POSTMethodWithHttps(String url, Map<String, String> headerParam, Map<String, Object> argsMap, String content) throws Exception {
        // 全部信任 不做身份鉴定
        SSLContextBuilder builder = new SSLContextBuilder();
        builder.loadTrustMaterial(null, (x509Certificates, s) -> true);
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(), new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new PlainConnectionSocketFactory())
                .register("https", sslsf)
                .build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        //max connection
        cm.setMaxTotal(10);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .setConnectionManager(cm)
                .setConnectionManagerShared(true)
                .build();
        HttpPost httpPost = new HttpPost(url);
        if (MapUtils.isNotEmpty(argsMap)) {
            // 设置参数
            UrlEncodedFormEntity encodedFormEntity = new UrlEncodedFormEntity(setHttpParams(argsMap), "UTF-8");
            httpPost.setEntity(encodedFormEntity);
        }
        if (StringUtils.isNotEmpty(content)) {
            httpPost.setEntity(new ByteArrayEntity(content.getBytes()));
        }
        //header
        if (MapUtils.isNotEmpty(headerParam)) {
            Set entrySet = headerParam.entrySet();
            Iterator entryIter = entrySet.iterator();
            while (entryIter.hasNext()) {
                Map.Entry entry = (Map.Entry) entryIter.next();
                String key = (String) entry.getKey();
                String value = (String) entry.getValue();
                httpPost.setHeader(key, value);
            }
        }
        // 执行请求
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        // 获取返回的数据
        HttpEntity httpEntity = httpResponse.getEntity();
        byte[] dataByte = null;
        if (httpEntity != null) {
            dataByte = getData(httpEntity);
            httpPost.abort();
        }
        httpResponse.close();
        httpClient.close();
        // 将字节数组转换成为字符串
        return bytesToString(dataByte);
    }

    public static String GETMethod(String url)
            throws Exception {
        byte[] dataByte = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet httpGet = new HttpGet(url);
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    dataByte = getData(httpEntity);
                    httpGet.abort();
                }
                return bytesToString(dataByte);
            }
        }
    }

    public static String POSTMethod(String url, String content, String flag) throws Exception {
        byte[] dataByte = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            if("application/json".equals(flag)){
                httpPost.setHeader("Content-type", "application/json; charset=utf-8");
            }
            if (StringUtils.isNotEmpty(content)) {
                httpPost.setEntity(new ByteArrayEntity(content.getBytes("utf-8")));
            }
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) {
                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    dataByte = getData(httpEntity);
                    httpPost.abort();
                }

                return bytesToString(dataByte);
            }
        }
    }

    public static String POSTHeaderMethod(String url, Map<String, String> headerParam, String content) throws Exception {
        byte[] dataByte = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            if (StringUtils.isNotEmpty(content)) {
                httpPost.setEntity(new ByteArrayEntity(content.getBytes("utf-8")));
            }
            if (MapUtils.isNotEmpty(headerParam)) {
                Set entrySet = headerParam.entrySet();
                Iterator entryIter = entrySet.iterator();
                while (entryIter.hasNext()) {
                    Map.Entry entry = (Map.Entry) entryIter.next();
                    String key = (String) entry.getKey();
                    String value = (String) entry.getValue();
                    httpPost.setHeader(key, value);
                }
            }

            try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) {

                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    byte[] responseBytes = getData(httpEntity);
                    dataByte = responseBytes;
                    httpPost.abort();
                }

                String result = bytesToString(dataByte);
                return result;
            }
        }
    }

    public static String GETHeaderMethod(String url, Map<String, String> headerParam)
            throws Exception {
        byte[] dataByte = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet httpGet = new HttpGet(url);

            if (MapUtils.isNotEmpty(headerParam)) {
                Set entrySet = headerParam.entrySet();
                Iterator entryIter = entrySet.iterator();
                while (entryIter.hasNext()) {
                    Map.Entry entry = (Map.Entry) entryIter.next();
                    String key = (String) entry.getKey();
                    String value = (String) entry.getValue();
                    httpGet.setHeader(key, value);
                }
            }

            try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
                HttpEntity httpEntity = httpResponse.getEntity();
                if (httpEntity != null) {
                    dataByte = getData(httpEntity);
                    httpGet.abort();
                }

                return bytesToString(dataByte);
            }
        }
    }

    /**微信请求专用（含编码“ISO-8859-1”）
     * https 请求处理方法，返回BYTE[]数组
     * 请注意，无论method为post还是get，请将请求体放入form_data，当method为get的时候，form_data中的数据
     * 将自动添加到url问号之后。也就是说，url中只能包含http(s)://<host>:[port]/<resource>，不需要包含问号
     * 以及问号之后的数据，如果要发送get请求，请将get请求的数据放入form_data中，切记
     * <p>
     * 注意：对方的接口要求的Authorization身份验证信息不需要在外部计算了，这个函数已经集成了此功能
     *
     * @param urlStr   请求地址
     * @param headers  http中的请求头(们)
     * @param formData 要提交的表单数据(post或者get数据)
     * @param method   请求的方式
     * @return 数据，如果请求失败、目标地址返回空字符串则函数返回null
     */
    public static byte[] httpsRequestPostWX(String urlStr, Map<String, String> headers, Map<String, String> formData, String method) throws Exception {
        String result = "";

        String post = "POST";
        String get = "GET";
        if (StringUtils.isBlank(method)) {
            return null;
        }

        if (StringUtils.isBlank(urlStr)) {
            return null;
        }

        if (!post.equalsIgnoreCase(method) && !get.equalsIgnoreCase(method)) {
            return null;
        }
        String query = "";
        if (formData != null) {
            for (String key : formData.keySet()) {
                query += key + "=" + URLEncoder.encode(formData.get(key), "UTF-8") + "&";
            }
        }

        if (query.endsWith("&")) {
            query = query.substring(0, query.length() - 1);
        }
        URL url = null;
        if (get.equalsIgnoreCase(method) && query.length() > 0) {
            url = new URL(urlStr + "?" + query);
        } else {
            url = new URL(urlStr);
        }

        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setConnectTimeout(2500);
        conn.setReadTimeout(5000);

        // 客户端不验证服务器的证书是不是合法的
        SSLContext sslContext = SSLContext.getInstance("SSL");
        TrustManager[] tm = {new MyX509TrustManager()};
        sslContext.init(null, tm, null);
        javax.net.ssl.SSLSocketFactory ssf = sslContext.getSocketFactory();
        conn.setSSLSocketFactory(ssf);
        conn.setHostnameVerifier(NoopHostnameVerifier.INSTANCE);
        conn.setRequestMethod(method);
        conn.setRequestProperty("Content-length", String.valueOf(query.length()));
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
        conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0;Windows98;DigExt)");


        // 从请求的url中提取host\resource部分（后面构建服务器验证信息的时候需要用到）
        String urlWithoutDomainName = urlStr;
        // 如果url是https://或者http://开头，那么这里先吧这一段字符串日翻，只剩下
        // host:[port]/resource部分
        if (urlWithoutDomainName.toUpperCase().startsWith("HTTPS://")) {
            urlWithoutDomainName = urlWithoutDomainName.substring(8);
        }
        if (urlWithoutDomainName.toUpperCase().startsWith("HTTP://")) {
            urlWithoutDomainName = urlWithoutDomainName.substring(7);
        }
        // 用/进行分割字符串，如果得到的字符串数组里面只有一个元素，说明url中只提供了
        // <host>:[port]部分，将值直接设置为/即可，如果大于1，说明有主机、端口、资源
        // 等信息，此处拿到array中第一项（肯定是<host>[:port]），将其从url剩下的字符串中
        // 删除，那么得到的将会是没有协议类型、主机、端口、的纯资源路径字符串，ok世界清静了
        String[] domainList = urlWithoutDomainName.split("/");
        if (domainList.length == 1) {
            urlWithoutDomainName = "/";
        } else if (domainList.length > 1) {
            urlWithoutDomainName = urlWithoutDomainName.substring(domainList[0].length());
        } else {
            // 如果主机都没有提供，那么还发个毛线的请求啊，直接退出
            return null;
        }

        // 无论是get还是post都应该将header放入http请求
        if (headers != null && headers.size() > 0) {
            // 把headers中的数据写入http请求中
            for (String head : headers.keySet()) {
                conn.setRequestProperty(head, headers.get(head));
            }
        }

        conn.setDoInput(true);
        conn.setDoOutput(post.equals(method.toUpperCase()));

        // 如果是POST请求，那么query中的内容从输出流中发送到服务器
        if (post.equals(method.toUpperCase())) {
            DataOutputStream output = new DataOutputStream(conn.getOutputStream());
            output.writeBytes(query);
            output.close();
        }

        conn.connect();
        int responseCode = conn.getResponseCode();

        /* Returns true if the code is in [200..300), which means the request was successfully received,
         understood, and accepted.*/
        if (responseCode >= 200 && responseCode < 300) {
            // 从服务器的返回缓冲区中读取数据
            DataInputStream input = new DataInputStream(conn.getInputStream());
            for (int c = input.read(); c != -1; c = input.read()) {
                result += (char) c;
            }
            input.close();
            if (StringUtils.isBlank(result)) {
                return null;
            }
            return result.getBytes("ISO-8859-1");
        }
        return null;
    }

}
